#include <sstream>
#include <iostream>
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
#include <Timer.hpp>
#include <AccuDedup.hpp>
#include <PrettyPrints.hpp>
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
using namespace std;
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void AccuDedupThread::HugeDedup( const uint64_t currPassIdx ) {
    // dedup to 2MB granularity
    
    huge->SetLine( hugeIdx, line );
    hugeIdx++;
    
    if( hugeIdx == linesPerHuge ) {
        
        // reset hugeIdx:
        hugeIdx = 0;
        
        // count total number of huge pages:
        hugeCounts.IncrementTotal( 1, kernHugeRC, userHugeRC );
        
        if( huge->IsZero() ) {
            hugeCounts.IncrementZero( 1, kernHugeRC, userHugeRC );
        }
        else {
        
            const uint64_t hash = hf->hash( huge->FirstNonZeroRawPtr(), wordsPerLine );
            const uint64_t hugePassIdx = hash % numPasses;
            
            if( hugePassIdx == currPassIdx ) {
                
                const uint64_t pagehash = hf->hash( huge->RawPtr(), wordsPerHuge );                            
                hg->IncCountByKey( &pagehash, 1, kernHugeRC, userHugeRC );
            }
        }
    }
}
void AccuDedupThread::PageDedup( const uint64_t currPassIdx ) {
    // dedup to 4kB granularity
    
    page->SetLine( lineIdx, line );
    lineIdx++;
    
    if( lineIdx == linesPerPage ) {
        
        // reset lineIdx:
        lineIdx = 0;
        
        // count total number of pages:
        pageCounts.IncrementTotal( 1, kernPageRC, userPageRC );
        
        // do the dedup:
        if( page->IsZero() ) {
            pageCounts.IncrementZero( 1, kernPageRC, userPageRC );
        }
        else {

            const uint64_t hash = hf->hash( page->FirstNonZeroRawPtr(), wordsPerLine );
            const uint64_t pagePassIdx = hash % numPasses;

            if( pagePassIdx == currPassIdx ) {

                const uint64_t pagehash = hf->hash( page->RawPtr(), wordsPerPage );
                pg->IncCountByKey( &pagehash, 1, kernPageRC, userPageRC );
            }
        }
    }
}
void AccuDedupThread::LineDedup( const uint64_t currPassIdx ) {
    // dedup to 64B granularity
    
    lineCounts.IncrementTotal( 1, kernPageRC, userPageRC );

    if( line->IsZero() ) {
        lineCounts.IncrementZero( 1, kernPageRC, userPageRC );
    }
    else {
        const uint64_t hash = hf->hash( line->RawPtr(), wordsPerLine );
        const uint64_t linePassIdx = hash % numPasses;
        
        if( linePassIdx == currPassIdx ) {
            fg->IncCountByKey( line->RawPtr(), 1, kernPageRC, userPageRC );
        }
    }
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
static __attribute__ ((noinline)) void * AccuDedupThreadLaunch( void * ctx ) {
    AccuDedupThread * dd = ( AccuDedupThread * ) ctx;
    dd->LoadMemImages();
    return NULL;
}
void LaunchAndJoinThreads( vector< pthread_t * > & pthreads, void *(*start_routine) (void *), vector< AccuDedupThread * > ddthreads, const uint32_t num2launch ) {
    for( uint32_t tidx = 0; tidx < num2launch; tidx++ ) {
        pthread_create( pthreads[ tidx ], NULL, start_routine, ddthreads[ tidx ] );
    }
    for( uint32_t tidx = 0; tidx < num2launch; tidx++ ) {
        pthread_join( *pthreads[ tidx ], NULL );
    }
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void AccuDedupMaster::AccuDedup( void ) {
    LaunchAndJoinThreads( pthreads, AccuDedupThreadLaunch, fddthreads, numThreads );
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void AccuDedupMaster::DeallocKeysAndSigs( void ) {
    fg->FreeKeysAndSigs();
    pg->FreeKeysAndSigs();
    hg->FreeKeysAndSigs();
}
void AccuDedupMaster::ClearMaps( void ) {
    fg->Clear();
    pg->Clear();
    hg->Clear();
}
void AccuDedupMaster::RunDedup( void ) {
    
    // divide the files amongst the threads:
    DivideFilesByThreads( bddthreads );
    
    // to save memory, the dedup can be performed in multiple passes
    // only a subset the lines (pages) is dedup'd on a given pass
    // that subset is chosen based on hash( line ) % numPasses
    for( uint64_t passIdx = 0ULL; passIdx < numPasses; passIdx++ ) {

        ResetProgressMeter();
        ClearMaps();
        AccuDedup();
        fg->Audit();
        pg->Audit();
        hg->Audit();
        AccumCounts();
        BuildRCHisto();
    }
    
    DeallocKeysAndSigs();
    PrintStats();
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

